<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn? | TBlog</title>
    <meta name="description" content="TBlog with vitePress">
    <link rel="stylesheet" href="/interview/assets/style.9156a4ed.css">
    <link rel="modulepreload" href="/interview/assets/Home.b86443e2.js">
    <link rel="modulepreload" href="/interview/assets/app.49accf37.js">
    <link rel="modulepreload" href="/interview/assets/npm_index.md.59b033b6.lean.js">
    <link rel="modulepreload" href="/interview/assets/app.49accf37.js">
    <link rel="icon" href="/favicon.ico">
    <meta name="twitter:title" content="关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn? | TBlog">
    <meta property="og:title" content="关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn? | TBlog">
  </head>
  <body>
    <div id="app"><!--[--><div class="theme"><header class="nav-bar" data-v-7161a24b><div class="sidebar-button" data-v-7161a24b><svg class="icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z" class></path></svg></div><a class="nav-bar-title" href="/interview/" aria-label="TBlog, back to home" data-v-7161a24b data-v-4a583abe><!----> TBlog</a><div class="flex-grow" data-v-7161a24b></div><div class="nav" data-v-7161a24b><nav class="nav-links" data-v-7161a24b data-v-15acbf05><!--[--><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="http://101.43.7.188:66" target="_blank" rel="noopener noreferrer" data-v-641633f9>Music <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="http://101.43.7.188:80" target="_blank" rel="noopener noreferrer" data-v-641633f9>CMC <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="https://juejin.cn/user/2163479676978734/posts" target="_blank" rel="noopener noreferrer" data-v-641633f9>掘金 <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="https://gitee.com/tu_zhiwei98" target="_blank" rel="noopener noreferrer" data-v-641633f9>Gitee <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><!--]--><!----><!----></nav></div><!--[--><!--]--></header><aside class="sidebar" data-v-6b49cdcd><nav class="nav-links nav" data-v-6b49cdcd data-v-15acbf05><!--[--><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="http://101.43.7.188:66" target="_blank" rel="noopener noreferrer" data-v-641633f9>Music <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="http://101.43.7.188:80" target="_blank" rel="noopener noreferrer" data-v-641633f9>CMC <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="https://juejin.cn/user/2163479676978734/posts" target="_blank" rel="noopener noreferrer" data-v-641633f9>掘金 <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><div class="item" data-v-15acbf05><div class="nav-link" data-v-15acbf05 data-v-641633f9><a class="item isExternal" href="https://gitee.com/tu_zhiwei98" target="_blank" rel="noopener noreferrer" data-v-641633f9>Gitee <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewbox="0 0 100 100" width="15" height="15" data-v-641633f9><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path><polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></div></div><!--]--><!----><!----></nav><!--[--><!--]--><ul class="sidebar-links" data-v-6b49cdcd><!--[--><li class="sidebar-link"><p class="sidebar-link-item">Vue3.2X</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/">基本语法&amp;使用</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/VueX">VueX4.x</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/Router">Router4.x</a><!----></li><li class="sidebar-link"><p class="sidebar-link-item">部分源码解析</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/reactive">响应式系统</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/source/Watch">Watch</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vue/source/nextTick">nextTick</a><!----></li></ul></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">WebPack</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/WebPack/">基础配置</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/WebPack/optimize">优化方案</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/WebPack/common">常见的Loader以及Plugin</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/WebPack/custom">自定义Loader以及Plugin</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/WebPack/advanced">手写简单的MY_Webpack</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">Javascript</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Javascript/">基本语法</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Javascript/advanced">高级语法</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">TypeScript</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/TypeScript/">基本语法</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/TypeScript/combat">业务实战</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/TypeScript/advanced">高阶使用</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">浏览器相关</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Browser/">基本原理</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Browser/plugin">浏览器插件</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Browser/advanced">深入原理</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">计算机网络</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Network/">计算机网络</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Network/security">前端安全</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">HTML/CSS</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/CSS/">CSS</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/HTML/">HTML</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">Vite2.X</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vite/">基础配置</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Vite/optimize">原理以及优化</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">Node</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Node/">基本知识</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Node/pit">踩坑记录</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">其他前端工具</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Git/">Git</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Jenkins/">Jenkins</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item active" href="/interview/npm/">包管理工具</a><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="#关于现代包管理器的深度思考——为什么现在我更推荐-pnpm-而不是-npm-yarn">关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?</a><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="#一、什么是-pnpm">一、什么是 pnpm ?</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#二、特性概览">二、特性概览</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#三、依赖管理">三、依赖管理</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#pnpm-依赖管理">pnpm 依赖管理</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#四、再谈安全">四、再谈安全</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#五、日常使用">五、日常使用</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#pnpm-install">pnpm install</a><!----></li></ul></li><li class="sidebar-link"><a class="sidebar-link-item" href="#npm-link">npm link</a><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="#相同目录下的链接">相同目录下的链接</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#不同目录下的链接">不同目录下的链接</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#解除链接">解除链接</a><!----></li></ul></li><li class="sidebar-link"><a class="sidebar-link-item" href="#npx-的使用">npx 的使用</a><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="#调用项目安装的模块">调用项目安装的模块</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#避免全局安装模块">避免全局安装模块</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#no-install-参数和-ignore-existing-参数">--no-install 参数和--ignore-existing 参数</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#使用不同版本的-node">使用不同版本的 node</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#p-参数">-p 参数</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#c-参数">-c 参数</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="#执行-github-源码">执行 GitHub 源码</a><!----></li></ul></li></ul></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">踩坑记录以及经验</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Pit/">踩坑</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/solution/">一些解决方案</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">面经</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Interview/">xxx面试</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Interview/two">xxX面试</a><!----></li></ul></li><li class="sidebar-link"><p class="sidebar-link-item">个人学习历程</p><ul class="sidebar-links"><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Myself/">个人情况说明</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Myself/pre">入行工作前</a><!----></li><li class="sidebar-link"><a class="sidebar-link-item" href="/interview/Myself/one">工作第一年</a><!----></li></ul></li><!--]--></ul><!--[--><!--]--></aside><!-- TODO: make this button accessible --><div class="sidebar-mask"></div><main class="page" data-v-7eddb2c4><div class="container" data-v-7eddb2c4><!--[--><!--]--><div style="position:relative;" class="content" data-v-7eddb2c4><div><h2 id="关于现代包管理器的深度思考——为什么现在我更推荐-pnpm-而不是-npm-yarn"><a class="header-anchor" href="#关于现代包管理器的深度思考——为什么现在我更推荐-pnpm-而不是-npm-yarn" aria-hidden="true">#</a> 关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn?</h2><p>这篇文章给大家分享一个业内一款出色的包管理器——<code>pnpm</code>。目前 GitHub 已经有 star 9.8k，现在已经相对成熟且稳定了。它由 npm/yarn 衍生而来，但却解决了 npm/yarn 内部潜在的 bug，并且极大了地优化了性能，扩展了使用场景。下面是本文的思维导图: <img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa3336c91184433d9b94cd2f5b6b8a47~tplv-k3u1fbpfcp-watermark.awebp" alt="img"></p><h3 id="一、什么是-pnpm"><a class="header-anchor" href="#一、什么是-pnpm" aria-hidden="true">#</a> 一、什么是 pnpm ?</h3><p>pnpm 的<a href="https://link.juejin.cn/?target=https%3A%2F%2Fpnpm.js.org%2Fen%2F" target="_blank" rel="noopener noreferrer">官方文档</a>是这样说的:</p><blockquote><p>Fast, disk space efficient package manager</p></blockquote><p>因此，pnpm 本质上就是一个包管理器，这一点跟 npm/yarn 没有区别，但它作为杀手锏的两个优势在于:</p><ul><li>包安装速度极快；</li><li>磁盘空间利用非常高效。</li></ul><p>它的安装也非常简单。可以有多简单?</p><div class="language-js"><pre><code>npm i <span class="token operator">-</span>g pnpm
复制代码
</code></pre></div><h3 id="二、特性概览"><a class="header-anchor" href="#二、特性概览" aria-hidden="true">#</a> 二、特性概览</h3><h4 id="_1-速度快"><a class="header-anchor" href="#_1-速度快" aria-hidden="true">#</a> 1. 速度快</h4><p>pnpm 安装包的速度究竟有多快？先以 React 包为例来对比一下:</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4c13ce7ad65e4345a2be7bd750187d51~tplv-k3u1fbpfcp-watermark.awebp" alt="img"></p><p>可以看到，作为黄色部分的 pnpm，在绝多大数场景下，包安装的速度都是明显优于 npm/yarn，速度会比 npm/yarn 快 2-3 倍。</p><p>对 yarn 比较熟悉的同学可能会说，yarn 不是有 <a href="https://link.juejin.cn/?target=https%3A%2F%2Fclassic.yarnpkg.com%2Fen%2Fdocs%2Fpnp%2F" target="_blank" rel="noopener noreferrer">PnP 安装模式</a>吗？直接去掉 node_modules，将依赖包内容写在磁盘，节省了 node 文件 I/O 的开销，这样也能提升安装速度。（具体原理见<a href="https://link.juejin.cn/?target=https%3A%2F%2Floveky.github.io%2F2019%2F02%2F11%2Fyarn-pnp%2F" target="_blank" rel="noopener noreferrer">这篇文章</a>）</p><p>接下来，我们以这样一个<a href="https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fpnpm%2Fbenchmarks-of-javascript-package-managers" target="_blank" rel="noopener noreferrer">仓库</a>为例，我们来看一看 benchmark 数据，主要对比一下 <code>pnpm</code> 和 <code>yarn PnP</code>:</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c63047cccdf74e5c82cc5d8c2aef995a~tplv-k3u1fbpfcp-watermark.awebp" alt="img"></p><p>从中可以看到，总体而言，<code>pnpm</code> 的包安装速度还是明显优于 <code>yarn PnP</code>的。</p><h4 id="_2-高效利用磁盘空间"><a class="header-anchor" href="#_2-高效利用磁盘空间" aria-hidden="true">#</a> 2. 高效利用磁盘空间</h4><p>pnpm 内部使用<code>基于内容寻址</code>的文件系统来存储磁盘上所有的文件，这个文件系统出色的地方在于:</p><ul><li>不会重复安装同一个包。用 npm/yarn 的时候，如果 100 个项目都依赖 lodash，那么 lodash 很可能就被安装了 100 次，磁盘中就有 100 个地方写入了这部分代码。但在使用 pnpm 只会安装一次，磁盘中只有一个地方写入，后面再次使用都会直接使用 <code>hardlink</code>(硬链接，不清楚的同学详见<a href="https://link.juejin.cn/?target=https%3A%2F%2Fwww.cnblogs.com%2Fitech%2Farchive%2F2009%2F04%2F10%2F1433052.html" target="_blank" rel="noopener noreferrer">这篇文章</a>)。</li><li>即使一个包的不同版本，pnpm 也会极大程度地复用之前版本的代码。举个例子，比如 lodash 有 100 个文件，更新版本之后多了一个文件，那么磁盘当中并不会重新写入 101 个文件，而是保留原来的 100 个文件的 <code>hardlink</code>，仅仅写入那<code>一个新增的文件</code>。</li></ul><h4 id="_3-支持-monorepo"><a class="header-anchor" href="#_3-支持-monorepo" aria-hidden="true">#</a> 3. 支持 monorepo</h4><p>随着前端工程的日益复杂，越来越多的项目开始使用 monorepo。之前对于多个项目的管理，我们一般都是使用多个 git 仓库，但 monorepo 的宗旨就是用一个 git 仓库来管理多个子项目，所有的子项目都存放在根目录的<code>packages</code>目录下，那么一个子项目就代表一个<code>package</code>。如果你之前没接触过 monorepo 的概念，建议仔细看看<a href="https://link.juejin.cn/?target=https%3A%2F%2Fwww.perforce.com%2Fblog%2Fvcs%2Fwhat-monorepo" target="_blank" rel="noopener noreferrer">这篇文章</a>以及开源的 monorepo 管理工具<a href="https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Flerna%2Flerna%23readme" target="_blank" rel="noopener noreferrer">lerna</a>，项目目录结构可以参考一下 <a href="https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fbabel%2Fbabel" target="_blank" rel="noopener noreferrer">babel 仓库</a>。</p><p>pnpm 与 npm/yarn 另外一个很大的不同就是支持了 monorepo，体现在各个子命令的功能上，比如在根目录下 <code>pnpm add A -r</code>, 那么所有的 package 中都会被添加 A 这个依赖，当然也支持 <code>--filter</code>字段来对 package 进行过滤。</p><h4 id="_4-安全性高"><a class="header-anchor" href="#_4-安全性高" aria-hidden="true">#</a> 4. 安全性高</h4><p>之前在使用 npm/yarn 的时候，由于 node_module 的扁平结构，如果 A 依赖 B， B 依赖 C，那么 A 当中是可以直接使用 C 的，但问题是 A 当中并没有声明 C 这个依赖。因此会出现这种非法访问的情况。但 pnpm 脑洞特别大，自创了一套依赖管理方式，很好地解决了这个问题，保证了安全性，具体怎么体现<code>安全</code>、规避非法访问依赖的<code>风险</code>的，后面再来详细说说。</p><h3 id="三、依赖管理"><a class="header-anchor" href="#三、依赖管理" aria-hidden="true">#</a> 三、依赖管理</h3><h4 id="npm-yarn-install-原理"><a class="header-anchor" href="#npm-yarn-install-原理" aria-hidden="true">#</a> npm/yarn install 原理</h4><p>主要分为两个部分, 首先，执行 npm/yarn install 之后，<code>包如何到达项目 node_modules 当中</code>。其次，node_modules <code>内部如何管理依赖</code>。</p><p>执行命令后，首先会构建依赖树，然后针对每个节点下的包，会经历下面四个步骤:</p><ul><li><ol><li>将依赖包的版本区间解析为某个具体的版本号</li></ol></li><li><ol><li>下载对应版本依赖的 tar 包到本地离线镜像</li></ol></li><li><ol><li>将依赖从离线镜像解压到本地缓存</li></ol></li><li><ol><li>将依赖从缓存拷贝到当前目录的 node_modules 目录</li></ol></li></ul><p>然后，对应的包就会到达项目的<code>node_modules</code>当中。</p><p>那么，这些依赖在<code>node_modules</code>内部是什么样的目录结构呢，换句话说，项目的依赖树是什么样的呢？</p><p>在 <code>npm1</code>、<code>npm2</code> 中呈现出的是嵌套结构，比如下面这样:</p><div class="language-js"><pre><code>node_modules
└─ foo
   ├─ index<span class="token punctuation">.</span>js
   ├─ <span class="token keyword">package</span><span class="token punctuation">.</span>json
   └─ node_modules
      └─ bar
         ├─ index<span class="token punctuation">.</span>js
         └─ <span class="token keyword">package</span><span class="token punctuation">.</span>json
复制代码
</code></pre></div><p>如果 <code>bar</code> 当中又有依赖，那么又会继续嵌套下去。试想一下这样的设计存在什么问题:</p><ol><li>依赖层级太深，会导致文件路径过长的问题，尤其在 window 系统下。</li><li>大量重复的包被安装，文件体积超级大。比如跟 <code>foo</code> 同级目录下有一个<code>baz</code>，两者都依赖于同一个版本的<code>lodash</code>，那么 lodash 会分别在两者的 node_modules 中被安装，也就是重复安装。</li><li>模块实例不能共享。比如 React 有一些内部变量，在两个不同包引入的 React 不是同一个模块实例，因此无法共享内部变量，导致一些不可预知的 bug。</li></ol><p>接着，从 npm3 开始，包括 yarn，都着手来通过<code>扁平化依赖</code>的方式来解决这个问题。相信大家都有这样的体验，我明明就装个 <code>express</code>，为什么 <code>node_modules</code>里面多了这么多东西？</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8eb0bebbcb544e5fadab18348a462035~tplv-k3u1fbpfcp-watermark.awebp" alt="img"></p><p>没错，这就是<code>扁平化</code>依赖管理的结果。相比之前的<code>嵌套结构</code>，现在的目录结构类似下面这样:</p><div class="language-js"><pre><code>node_modules
├─ foo
<span class="token operator">|</span>  ├─ index<span class="token punctuation">.</span>js
<span class="token operator">|</span>  └─ <span class="token keyword">package</span><span class="token punctuation">.</span>json
└─ bar
   ├─ index<span class="token punctuation">.</span>js
   └─ <span class="token keyword">package</span><span class="token punctuation">.</span>json
复制代码
</code></pre></div><p>所有的依赖都被拍平到<code>node_modules</code>目录下，不再有很深层次的嵌套关系。这样在安装新的包时，根据 node require 机制，会不停往上级的<code>node_modules</code>当中去找，如果找到相同版本的包就不会重新安装，解决了大量包重复安装的问题，而且依赖层级也不会太深。</p><p>之前的问题是解决了，但仔细想想这种<code>扁平化</code>的处理方式，它真的就是无懈可击吗？并不是。它照样存在诸多问题，梳理一下:</p><ul><li><ol><li>依赖结构的<strong>不确定性</strong>。</li></ol></li><li><ol><li>扁平化算法本身的<strong>复杂性</strong>很高，耗时较长。</li></ol></li><li><ol><li>项目中仍然可以<strong>非法访问</strong>没有声明过依赖的包</li></ol></li></ul><p>后面两个都好理解，那第一点中的<code>不确定性</code>是什么意思？这里来详细解释一下。</p><p>假如现在项目依赖两个包 foo 和 bar，这两个包的依赖又是这样的: <img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/211c86c7838b48bead2a4ee7faee25b1~tplv-k3u1fbpfcp-watermark.awebp" alt="img"></p><p>那么 npm/yarn install 的时候，通过扁平化处理之后，究竟是这样 <img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b2368d74f8b341f0b1b545198683af59~tplv-k3u1fbpfcp-watermark.awebp" alt="img"></p><p>还是这样？</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/198d6c12f0e045ce8c0867b44b3aaeb1~tplv-k3u1fbpfcp-watermark.awebp" alt="img"></p><p>答案是: 都有可能。取决于 foo 和 bar 在 <code>package.json</code>中的位置，如果 foo 声明在前面，那么就是前面的结构，否则是后面的结构。</p><p>这就是为什么会产生依赖结构的<code>不确定</code>问题，也是 <code>lock 文件</code>诞生的原因，无论是<code>package-lock.json</code>(npm 5.x 才出现)还是<code>yarn.lock</code>，都是为了保证 install 之后都产生确定的<code>node_modules</code>结构。</p><p>尽管如此，npm/yarn 本身还是存在<code>扁平化算法复杂</code>和<code>package 非法访问</code>的问题，影响性能和安全。</p><h3 id="pnpm-依赖管理"><a class="header-anchor" href="#pnpm-依赖管理" aria-hidden="true">#</a> pnpm 依赖管理</h3><p>pnpm 的作者<code>Zoltan Kochan</code>发现 yarn 并没有打算去解决上述的这些问题，于是另起炉灶，写了全新的包管理器，开创了一套新的依赖管理机制，现在就让我们去一探究竟。</p><p>还是以安装 <code>express</code> 为例，我们新建一个目录，执行:</p><div class="language-"><pre><code>pnpm init -y
复制代码
</code></pre></div><p>然后执行:</p><div class="language-"><pre><code>pnpm install express
复制代码
</code></pre></div><p>我们再去看看<code>node_modules</code>:</p><div class="language-"><pre><code>.pnpm
.modules.yaml
express
复制代码
</code></pre></div><p>我们直接就看到了<code>express</code>，但值得注意的是，这里仅仅只是一个<code>软链接</code>，不信你打开看看，里面并没有 node_modules 目录，如果是真正的文件位置，那么根据 node 的包加载机制，它是找不到依赖的。那么它真正的位置在哪呢？</p><p>我们继续在 .pnpm 当中寻找:</p><div class="language-"><pre><code>▾ node_modules
  ▾ .pnpm
    ▸ accepts@1.3.7
    ▸ array-flatten@1.1.1
    ...
    ▾ express@4.17.1
      ▾ node_modules
        ▸ accepts
        ▸ array-flatten
        ▸ body-parser
        ▸ content-disposition
        ...
        ▸ etag
        ▾ express
          ▸ lib
            History.md
            index.js
            LICENSE
            package.json
            Readme.md
复制代码
</code></pre></div><p>好家伙！竟然在 <code>.pnpm/express@4.17.1/node_modules/express</code>下面找到了!</p><p>随便打开一个别的包:</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/163b08df949944fea45012bc0c6bfa9c~tplv-k3u1fbpfcp-watermark.awebp" alt="img"></p><p>好像也都是一样的规律，都是<code>&lt;package-name&gt;@version/node_modules/&lt;package-name&gt;</code>这种目录结构。并且 express 的依赖都在<code>.pnpm/express@4.17.1/node_modules</code>下面，这些依赖也全都是<strong>软链接</strong>。</p><p>再看看<code>.pnpm</code>，<code>.pnpm</code>目录下虽然呈现的是扁平的目录结构，但仔细想想，顺着<code>软链接</code>慢慢展开，其实就是嵌套的结构！</p><div class="language-"><pre><code>▾ node_modules
  ▾ .pnpm
    ▸ accepts@1.3.7
    ▸ array-flatten@1.1.1
    ...
    ▾ express@4.17.1
      ▾ node_modules
        ▸ accepts  -&gt; ../accepts@1.3.7/node_modules/accepts
        ▸ array-flatten -&gt; ../array-flatten@1.1.1/node_modules/array-flatten
        ...
        ▾ express
          ▸ lib
            History.md
            index.js
            LICENSE
            package.json
            Readme.md
复制代码
</code></pre></div><p>将<code>包本身</code>和<code>依赖</code>放在同一个<code>node_module</code>下面，与原生 Node 完全兼容，又能将 package 与相关的依赖很好地组织到一起，设计十分精妙。</p><p>现在我们回过头来看，根目录下的 node_modules 下面不再是眼花缭乱的依赖，而是跟 package.json 声明的依赖基本保持一致。即使 pnpm 内部会有一些包会设置依赖提升，会被提升到根目录 node_modules 当中，但整体上，根目录的<code>node_modules</code>比以前还是清晰和规范了许多。</p><h3 id="四、再谈安全"><a class="header-anchor" href="#四、再谈安全" aria-hidden="true">#</a> 四、再谈安全</h3><p>不知道你发现没有，pnpm 这种依赖管理的方式也很巧妙地规避了<code>非法访问依赖</code>的问题，也就是只要一个包未在 package.json 中声明依赖，那么在项目中是无法访问的。</p><p>但在 npm/yarn 当中是做不到的，那你可能会问了，如果 A 依赖 B， B 依赖 C，那么 A 就算没有声明 C 的依赖，由于有依赖提升的存在，C 被装到了 A 的<code>node_modules</code>里面，那我在 A 里面用 C，跑起来没有问题呀，我上线了之后，也能正常运行啊。不是挺安全的吗？</p><p>还真不是。</p><p>第一，你要知道 B 的版本是可能随时变化的，假如之前依赖的是<code>C@1.0.1</code>，现在发了新版，新版本的 B 依赖 <code>C@2.0.1</code>，那么在项目 A 当中 npm/yarn install 之后，装上的是 2.0.1 版本的 C，而 A 当中用的还是 C 当中旧版的 API，可能就直接报错了。</p><p>第二，如果 B 更新之后，可能不需要 C 了，那么安装依赖的时候，C 都不会装到<code>node_modules</code>里面，A 当中引用 C 的代码直接报错。</p><p>还有一种情况，在 monorepo 项目中，如果 A 依赖 X，B 依赖 X，还有一个 C，它不依赖 X，但它代码里面用到了 X。由于依赖提升的存在，npm/yarn 会把 X 放到根目录的 node_modules 中，这样 C 在本地是能够跑起来的，因为根据 node 的包加载机制，它能够加载到 monorepo 项目根目录下的 node_modules 中的 X。但试想一下，一旦 C 单独发包出去，用户单独安装 C，那么就找不到 X 了，执行到引用 X 的代码时就直接报错了。</p><p>这些，都是依赖提升潜在的 bug。如果是自己的业务代码还好，试想一下如果是给很多开发者用的工具包，那危害就非常严重了。</p><p>npm 也有想过去解决这个问题，指定<code>--global-style</code>参数即可禁止变量提升，但这样做相当于回到了当年嵌套依赖的时代，一夜回到解放前，前面提到的嵌套依赖的缺点仍然暴露无遗。</p><p>npm/yarn 本身去解决依赖提升的问题貌似很难完成，不过社区针对这个问题也已经有特定的解决方案: <strong>dependency-check</strong>，地址: <a href="https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fdependency-check-team%2Fdependency-check" target="_blank" rel="noopener noreferrer">github.com/dependency-…</a></p><p>但不可否认的是，pnpm 做的更加彻底，独创的一套依赖管理方式不仅解决了依赖提升的安全问题，还大大优化了时间和空间上的性能。</p><h3 id="五、日常使用"><a class="header-anchor" href="#五、日常使用" aria-hidden="true">#</a> 五、日常使用</h3><p>说了这么多，估计你会觉得 <code>pnpm</code> 挺复杂的，是不是用起来成本很高呢？</p><p>恰好相反，pnpm 使用起来十分简单，如果你之前有 npm/yarn 的使用经验，甚至可以无缝迁移到 pnpm 上来。不信我们来举几个日常使用的例子。</p><h3 id="pnpm-install"><a class="header-anchor" href="#pnpm-install" aria-hidden="true">#</a> pnpm install</h3><p>跟 npm install 类似，安装项目下所有的依赖。但对于 monorepo 项目，会安装 workspace 下面所有 packages 的所有依赖。不过可以通过 --filter 参数来指定 package，只对满足条件的 package 进行依赖安装。</p><p>当然，也可以这样使用，来进行单个包的安装:</p><div class="language-js"><pre><code><span class="token comment">// 安装 axios</span>
pnpm install axios
<span class="token comment">// 安装 axios 并将 axios 添加至 devDependencies</span>
pnpm install axios <span class="token operator">-</span><span class="token constant">D</span>
<span class="token comment">// 安装 axios 并将 axios 添加至 dependencies</span>
pnpm install axios <span class="token operator">-</span><span class="token constant">S</span>
复制代码
</code></pre></div><p>当然，也可以通过 --filter 来指定 package。</p><h4 id="pnpm-update"><a class="header-anchor" href="#pnpm-update" aria-hidden="true">#</a> pnpm update</h4><p>根据指定的范围将包更新到最新版本，monorepo 项目中可以通过 --filter 来指定 package。</p><h4 id="pnpm-uninstall"><a class="header-anchor" href="#pnpm-uninstall" aria-hidden="true">#</a> pnpm uninstall</h4><p>在 node_modules 和 package.json 中移除指定的依赖。monorepo 项目同上。举例如下:</p><div class="language-"><pre><code>// 移除 axios
pnpm uninstall axios --filter package-a
复制代码
</code></pre></div><h4 id="pnpm-link"><a class="header-anchor" href="#pnpm-link" aria-hidden="true">#</a> pnpm link</h4><p>将本地项目连接到另一个项目。注意，使用的是硬链接，而不是软链接。如:</p><div class="language-"><pre><code>pnpm link ../../axios
复制代码
</code></pre></div><p>另外，对于我们经常用到<code>npm run/start/test/publish</code>，这些直接换成 pnpm 也是一样的，不再赘述。更多的使用姿势可参考官方文档: <a href="https://link.juejin.cn/?target=https%3A%2F%2Fpnpm.js.org%2Fen%2F" target="_blank" rel="noopener noreferrer">pnpm.js.org/en/</a></p><p>可以看到，虽然 pnpm 内部做了非常多复杂的设计，但实际上对于用户来说是无感知的，使用起来非常友好。并且，现在作者现在还一直在维护，目前 npm 上周下载量已经有 10w +，经历了大规模用户的考验，稳定性也能有所保障。</p><p>因此，综合来看，pnpm 是一个相比 npm/yarn 更优的方案，期待未来 pnpm 能有更多的落地。&#39;</p><h2 id="npm-link"><a class="header-anchor" href="#npm-link" aria-hidden="true">#</a> npm link</h2><p>在 Node 开发过程中随着代码规模的增加，会把一些基础模块封装成独立发布的 npm 包，供所有项目使用。但问题来了： <em>修改库包的代码后，如何与使用该库的项目代码集成测试？</em> 每次发布到 npmjs 或私库，都会导致版本号更新太频繁；用软连接，各个文件夹切来切去，还得找到 global npm 包的位置。 其实只需要使用 npm 命令<code>npm link</code>就能够实现。</p><h3 id="相同目录下的链接"><a class="header-anchor" href="#相同目录下的链接" aria-hidden="true">#</a> 相同目录下的链接</h3><ul><li>项目和模块在同一个目录下，可以直接使用相对路径链接</li></ul><div class="language-cpp"><pre><code><span class="token comment">// 进入项目目录</span>
$ cd path<span class="token operator">/</span>to<span class="token operator">/</span>my<span class="token operator">-</span>project
<span class="token comment">// 链接模块目录</span>
$ npm link path<span class="token operator">/</span>to<span class="token operator">/</span>my<span class="token operator">-</span><span class="token keyword">module</span>
</code></pre></div><ul><li><code>npm link</code> 操作会在项目的 <code>node_modules</code> 目录下创建一个 <code>module-name</code> 的超链接（类似 Windows 的快捷方式）或称 “symlink”，链接到 <code>path/to/my-module</code>。</li><li><code>path/to/my-project</code> 和 <code>path/to/my-module</code> 都是二者 <code>package.json</code> 文件所在的目录。</li><li><code>module-name</code> 依据 npm 模块的 <code>package.json</code> 指定。</li></ul><h3 id="不同目录下的链接"><a class="header-anchor" href="#不同目录下的链接" aria-hidden="true">#</a> 不同目录下的链接</h3><ul><li>项目和模块不在同一个目录下，需要先把模块链接到全局，然后再在项目中链接模块</li></ul><div class="language-cpp"><pre><code><span class="token comment">// 先去到模块目录，把它链接到全局</span>
$ cd path<span class="token operator">/</span>to<span class="token operator">/</span>my<span class="token operator">-</span><span class="token keyword">module</span>
$ npm link
</code></pre></div><ul><li><code>npm link</code> 操作会在全局 <code>node_modules</code> 目录（如 MacOS 默认的是 <code>/usr/local/lib/node_modules</code>）下创建一个 <code>module-name</code> 的超链接。</li></ul><div class="language-cpp"><pre><code><span class="token comment">// 再去项目目录</span>
$ cd path<span class="token operator">/</span>to<span class="token operator">/</span>my<span class="token operator">-</span>project
<span class="token comment">// 通过包名建立链接</span>
$ npm link <span class="token keyword">module</span><span class="token operator">-</span>name
</code></pre></div><ul><li>此时只需要指定 <code>module-name</code>，在项目的 <code>node_modules</code> 目录下创建一个 <code>module-name</code> 的超链接，链接到 <code>/usr/local/lib/node_modules/module-name</code>，然后再由全局目录下的超链接，链接到具体的代码目录下。</li></ul><h3 id="解除链接"><a class="header-anchor" href="#解除链接" aria-hidden="true">#</a> 解除链接</h3><ul><li>解除项目和模块的链接</li></ul><div class="language-cpp"><pre><code><span class="token comment">// 进入项目目录，解除链接</span>
$ cd path<span class="token operator">/</span>to<span class="token operator">/</span>my<span class="token operator">-</span>project
$ npm unlink <span class="token keyword">module</span><span class="token operator">-</span>name
</code></pre></div><ul><li>解除模块的全局链接</li></ul><div class="language-cpp"><pre><code><span class="token comment">// 进入模块目录，解除链接</span>
$ cd path<span class="token operator">/</span>to<span class="token operator">/</span>my<span class="token operator">-</span><span class="token keyword">module</span>
$ npm unlink <span class="token keyword">module</span><span class="token operator">-</span>name
</code></pre></div><h2 id="npx-的使用"><a class="header-anchor" href="#npx-的使用" aria-hidden="true">#</a> npx 的使用</h2><p>Node 自带 npm 模块，所以可以直接使用 npx 命令。万一不能用，就要手动安装一下。</p><blockquote><div class="language-bash"><pre><code>$ <span class="token function">npm</span> <span class="token function">install</span> -g npx
</code></pre></div></blockquote><h3 id="调用项目安装的模块"><a class="header-anchor" href="#调用项目安装的模块" aria-hidden="true">#</a> 调用项目安装的模块</h3><p>npx 想要解决的主要问题，就是调用项目内部安装的模块。比如，项目内部安装了测试工具 <a href="https://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html" target="_blank" rel="noopener noreferrer">Mocha</a>。</p><blockquote><div class="language-bash"><pre><code>$ <span class="token function">npm</span> <span class="token function">install</span> -D mocha
</code></pre></div></blockquote><p>一般来说，调用 Mocha ，只能在项目脚本和 package.json 的<a href="https://www.ruanyifeng.com/blog/2016/10/npm_scripts.html" target="_blank" rel="noopener noreferrer"><code>scripts</code></a>字段里面， 如果想在命令行下调用，必须像下面这样。</p><blockquote><div class="language-bash"><pre><code><span class="token comment"># 项目的根目录下执行</span>
$ node-modules/.bin/mocha --version
</code></pre></div></blockquote><p>npx 就是想解决这个问题，让项目内部安装的模块用起来更方便，只要像下面这样调用就行了。</p><blockquote><div class="language-bash"><pre><code>$ npx mocha --version
</code></pre></div></blockquote><p>npx 的原理很简单，就是运行的时候，会到<code>node_modules/.bin</code>路径和环境变量<code>$PATH</code>里面，检查命令是否存在。</p><p>由于 npx 会检查环境变量<code>$PATH</code>，所以系统命令也可以调用。</p><blockquote><div class="language-bash"><pre><code><span class="token comment"># 等同于 ls</span>
$ npx <span class="token function">ls</span>
</code></pre></div></blockquote><p>注意，Bash 内置的命令不在<code>$PATH</code>里面，所以不能用。比如，<code>cd</code>是 Bash 命令，因此就不能用<code>npx cd</code>。</p><h3 id="避免全局安装模块"><a class="header-anchor" href="#避免全局安装模块" aria-hidden="true">#</a> 避免全局安装模块</h3><p>除了调用项目内部模块，npx 还能避免全局安装的模块。比如，<code>create-react-app</code>这个模块是全局安装，npx 可以运行它，而且不进行全局安装。</p><blockquote><div class="language-bash"><pre><code>$ npx create-react-app my-react-app
</code></pre></div></blockquote><p>上面代码运行时，npx 将<code>create-react-app</code>下载到一个临时目录，使用以后再删除。所以，以后再次执行上面的命令，会重新下载<code>create-react-app</code>。</p><p>下载全局模块时，npx 允许指定版本。</p><blockquote><div class="language-bash"><pre><code>$ npx uglify-js@3.1.0 main.js -o ./dist/main.js
</code></pre></div></blockquote><p>上面代码指定使用 3.1.0 版本的<code>uglify-js</code>压缩脚本。</p><p>注意，只要 npx 后面的模块无法在本地发现，就会下载同名模块。比如，本地没有安装<code>http-server</code>模块，下面的命令会自动下载该模块，在当前目录启动一个 Web 服务。</p><blockquote><div class="language-bash"><pre><code>$ npx http-server
</code></pre></div></blockquote><h3 id="no-install-参数和-ignore-existing-参数"><a class="header-anchor" href="#no-install-参数和-ignore-existing-参数" aria-hidden="true">#</a> <code>--no-install</code> 参数和<code>--ignore-existing</code> 参数</h3><p>如果想让 npx 强制使用本地模块，不下载远程模块，可以使用<code>--no-install</code>参数。如果本地不存在该模块，就会报错。</p><blockquote><div class="language-bash"><pre><code>$ npx --no-install http-server
</code></pre></div></blockquote><p>反过来，如果忽略本地的同名模块，强制安装使用远程模块，可以使用<code>--ignore-existing</code>参数。比如，本地已经全局安装了<code>create-react-app</code>，但还是想使用远程模块，就用这个参数。</p><blockquote><div class="language-bash"><pre><code>$ npx --ignore-existing create-react-app my-react-app
</code></pre></div></blockquote><h3 id="使用不同版本的-node"><a class="header-anchor" href="#使用不同版本的-node" aria-hidden="true">#</a> 使用不同版本的 node</h3><p>利用 npx 可以下载模块这个特点，可以指定某个版本的 Node 运行脚本。它的窍门就是使用 npm 的 <a href="https://www.npmjs.com/package/node" target="_blank" rel="noopener noreferrer">node 模块</a>。</p><blockquote><div class="language-bash"><pre><code>$ npx node@0.12.8 -v
v0.12.8
</code></pre></div></blockquote><p>上面命令会使用 0.12.8 版本的 Node 执行脚本。原理是从 npm 下载这个版本的 node，使用后再删掉。</p><p>某些场景下，这个方法用来切换 Node 版本，要比 nvm 那样的版本管理器方便一些。</p><h3 id="p-参数"><a class="header-anchor" href="#p-参数" aria-hidden="true">#</a> <code>-p</code> 参数</h3><p><code>-p</code>参数用于指定 npx 所要安装的模块，所以上一节的命令可以写成下面这样。</p><blockquote><div class="language-bash"><pre><code>$ npx -p node@0.12.8 node -v
v0.12.8
</code></pre></div></blockquote><p>上面命令先指定安装<code>node@0.12.8</code>，然后再执行<code>node -v</code>命令。</p><p><code>-p</code>参数对于需要安装多个模块的场景很有用。</p><blockquote><div class="language-bash"><pre><code>$ npx -p lolcatjs -p cowsay <span class="token punctuation">[</span>command<span class="token punctuation">]</span>
</code></pre></div></blockquote><h3 id="c-参数"><a class="header-anchor" href="#c-参数" aria-hidden="true">#</a> -c 参数</h3><p>如果 npx 安装多个模块，默认情况下，所执行的命令之中，只有第一个可执行项会使用 npx 安装的模块，后面的可执行项还是会交给 Shell 解释。</p><blockquote><div class="language-bash"><pre><code>$ npx -p lolcatjs -p cowsay <span class="token string">&#39;cowsay hello | lolcatjs&#39;</span>
<span class="token comment"># 报错</span>
</code></pre></div></blockquote><p>上面代码中，<code>cowsay hello | lolcatjs</code>执行时会报错，原因是第一项<code>cowsay</code>由 npx 解释，而第二项命令<code>localcatjs</code>由 Shell 解释，但是<code>lolcatjs</code>并没有全局安装，所以报错。</p><p><code>-c</code>参数可以将所有命令都用 npx 解释。有了它，下面代码就可以正常执行了。</p><blockquote><div class="language-bash"><pre><code>$ npx -p lolcatjs -p cowsay -c <span class="token string">&#39;cowsay hello | lolcatjs&#39;</span>
</code></pre></div></blockquote><p><code>-c</code>参数的另一个作用，是将环境变量带入所要执行的命令。举例来说，npm 提供当前项目的一些环境变量，可以用下面的命令查看。</p><blockquote><div class="language-bash"><pre><code>$ <span class="token function">npm</span> run <span class="token function">env</span> <span class="token operator">|</span> <span class="token function">grep</span> npm_
</code></pre></div></blockquote><p><code>-c</code>参数可以把这些 npm 的环境变量带入 npx 命令。</p><blockquote><div class="language-bash"><pre><code>$ npx -c <span class="token string">&#39;echo &quot;$npm_package_name&quot;&#39;</span>
</code></pre></div></blockquote><p>上面代码会输出当前项目的项目名。</p><h3 id="执行-github-源码"><a class="header-anchor" href="#执行-github-源码" aria-hidden="true">#</a> 执行 GitHub 源码</h3><p>npx 还可以执行 GitHub 上面的模块源码。</p><blockquote><div class="language-bash"><pre><code><span class="token comment"># 执行 Gist 代码</span>
$ npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32

<span class="token comment"># 执行仓库代码</span>
$ npx github:piuccio/cowsay hello
</code></pre></div></blockquote><p>注意，远程代码必须是一个模块，即必须包含<code>package.json</code>和入口脚本。</p></div></div><footer class="page-footer" data-v-7eddb2c4 data-v-fb8d84c6><div class="edit" data-v-fb8d84c6><div class="edit-link" data-v-fb8d84c6 data-v-1ed99556><!----></div></div><div class="updated" data-v-fb8d84c6><!----></div></footer><div class="next-and-prev-link" data-v-7eddb2c4 data-v-38ede35f><div class="container" data-v-38ede35f><div class="prev" data-v-38ede35f><a class="link" href="/interview/Jenkins/" data-v-38ede35f><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="icon icon-prev" data-v-38ede35f><path d="M19,11H7.4l5.3-5.3c0.4-0.4,0.4-1,0-1.4s-1-0.4-1.4,0l-7,7c-0.1,0.1-0.2,0.2-0.2,0.3c-0.1,0.2-0.1,0.5,0,0.8c0.1,0.1,0.1,0.2,0.2,0.3l7,7c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3c0.4-0.4,0.4-1,0-1.4L7.4,13H19c0.6,0,1-0.4,1-1S19.6,11,19,11z"></path></svg><span class="text" data-v-38ede35f>Jenkins</span></a></div><div class="next" data-v-38ede35f><a class="link" href="/interview/Pit/" data-v-38ede35f><span class="text" data-v-38ede35f>踩坑</span><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" class="icon icon-next" data-v-38ede35f><path d="M19.9,12.4c0.1-0.2,0.1-0.5,0-0.8c-0.1-0.1-0.1-0.2-0.2-0.3l-7-7c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.3,5.3H5c-0.6,0-1,0.4-1,1s0.4,1,1,1h11.6l-5.3,5.3c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3l7-7C19.8,12.6,19.9,12.5,19.9,12.4z"></path></svg></a></div></div></div><!--[--><!--]--></div></main></div><!----><!--]--></div>
    <script>__VP_HASH_MAP__ = JSON.parse("{\"index.md\":\"992e2bc4\",\"css_advanced.md\":\"157298f3\",\"css_index.md\":\"0df1ef28\",\"browser_advanced.md\":\"a7a79457\",\"browser_index.md\":\"9938c247\",\"browser_plugin.md\":\"fd27046d\",\"git_index.md\":\"fb33a92a\",\"html_advanced.md\":\"599ad3d5\",\"html_index.md\":\"c94736b6\",\"interview_index.md\":\"07dc8acc\",\"interview_two.md\":\"0ca812f6\",\"javascript_advanced.md\":\"5c434e6d\",\"javascript_index.md\":\"d996a3fc\",\"jenkins_index.md\":\"3068e232\",\"myself_index.md\":\"340fc837\",\"myself_one.md\":\"31202d59\",\"myself_pre.md\":\"aa93248e\",\"network_index.md\":\"7251e8b9\",\"network_security.md\":\"d11dae3c\",\"node_index.md\":\"e0b22fc7\",\"node_pit.md\":\"a1502c64\",\"npm_index.md\":\"59b033b6\",\"pit_index.md\":\"603d46aa\",\"solution_index.md\":\"987aa705\",\"typescript_advanced.md\":\"431b8bba\",\"typescript_combat.md\":\"60c9a8a2\",\"typescript_index.md\":\"bd88b5a2\",\"vite_index.md\":\"d6838c03\",\"vite_optimize.md\":\"49f1bb24\",\"vue_index.md\":\"380ec221\",\"vue_reactive.md\":\"206c8e46\",\"vue_router.md\":\"0d556c25\",\"vue_source.md\":\"592e192a\",\"vue_vuex.md\":\"032502ef\",\"webpack_advanced.md\":\"ae6c63b7\",\"webpack_common.md\":\"141deb7a\",\"webpack_custom.md\":\"4827ebf8\",\"webpack_index.md\":\"49f185a6\",\"webpack_optimize.md\":\"545698f9\",\"vue_source_index.md\":\"1c12bc29\",\"vue_source_nexttick.md\":\"4533a28b\",\"vue_source_watch.md\":\"0e7342fc\"}")</script>
    <script type="module" async src="/interview/assets/app.49accf37.js"></script>
  </body>
</html>